%%% Copyright [heng] [litaocheng@gmail.com] 
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License. 
%%% You may obtain a copy of the License at 
%%%    http://www.apache.org/licenses/LICENSE-2.0 
%%% Unless required by applicable law or agreed to in writing, 
%%% software distributed under the License is distributed on an "AS IS" BASIS, 
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
%%% See the License for the specific language governing permissions and 
%%% limitations under the License. 

%%% @doc handle the jobs  
-module(heng_jobs_server).
-author('litaocheng@gmail.com').
-vsn('0.1').
-include("heng_master.hrl").

-behaviour(gen_server).

-export([start_link/0]).
%% jobs
-export([add/2, delete/1]).
-export([get_job/1]).
-export([all/0]).

%% with hengm_worker
-export([get_unfull_job/1]).
-export([notify_new_process/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2
        , code_change/3, terminate/2).

-record(jobinfo,
            {
                jobid :: job_id(),      %% the job id
                pid :: pid(),           %% the hengm_job pid 
                priority                %% the job priority (not use)
            }).

-record(state, 
            {             
                jobid = 1 :: pos_integer(), %% current job id
            }).            

-define(TAB_JOBS, tab_jobs).
-define(SERVER, ?MODULE).

%% @doc start the server
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%% @doc add job
-spec add(Name :: atom(), Code:: binary()) ->
    'ok' | {'error', any()}.
add(Name, Code) when is_binary(Code) ->
    ?Debug("~p add job:~p~n", [?MODULE, Name]),
    %% get the job config info
    case catch get_job_config(Name, Code) of
        {ok, JobConf} ->
            % generate a new job id
            Job = #job{id = new_jobid(), mod = Name, bin = Code, conf = JobConf},
            gen_server:call(?SERVER, {add, Job});
        {error, R} ->
            ?Error("job format error:~p~n", [R]),
            {error, {ejob_format, R}}
    end.

%% @doc delete a job 
-sepc delete(JobId :: job_id()) ->
    'ok' | {'error', ereason()}.
delete(JobId) ->
    ?Debug("~p delete job:~p~n", [?MODULE, JobId]),
    gen_server:call(?SERVER, {delete, JobId}).

%% @doc get job info
-spec get_job(JogId :: job_id()) ->
    'false' | {'ok', job()}.
get_job(JobId) ->
    gen_server:call(?SERVER, {get_job, JobId}).

%% @doc all jobs info
-spec all() ->
    list().
all() ->
    gen_server:call(?SERVER, {all}).

%% gen_server callbacks
init(_Args) ->
    process_flag(trapexit, true),
    ets:new(?TAB_JOBS, [named_table, set, protected, {keypos, #job_info.id}]),    
    {ok, #state{}}.

handle_call({new_jobid}, _From, State) ->
    JobId = State#state.jobid,
    State2 = State#state{jobid = JobId + 1},
    {reply, JobId, State2};
handle_call({add, JobConf, Job = #job{}}, _From, State) ->
    % spawn the new hengm_job
    Pid = do_new_job(Job),
    JobInfo = #job_info{jobid = Job#job.id, pid = Pid},
    ets:insert(?TAB_JOBS, JobInfo),
    {reply, ok, State};
handle_call({delete, JobId}, _From, State) ->
    Reply = do_delete_job(JobId, State),
    {reply, Reply, State};
handle_call({get_job, JobId}, _From, State) ->
    Reply = do_get_job(JobId, State),
    {reply, Reply, State};
handle_call({all}, _From, State) ->
    Reply = do_all(State),
    {reply, Reply, State};
handle_call(_Msg, _From, State) ->
    {noreply, State}.
handle_cast(_Msg, State) ->
    {noreply, State}.
handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_Old, State, _Extra) ->
    {ok, State}.


%%
%% internal API
%%

%% new job process
do_new_job(Job = #job{}) ->
    {ok, Pid} = gen_server:start_link(hengm_job, [Job], []),
    Pid.

%% get the config info from Job
get_job_config(Name, Code) ->
    case code:loadbinary(Name, "", Code) of
        {module, Name} ->
            [_|_] = Config = Name:job_config(),
            % after get the config info ,we purge the module from system
            true = code:purge(Name),
            {ok, Config};
        {error, R} ->
            {error, R}
    end.

%% generate the job id
new_jobid() ->
    gen_server:call(?SERVER, {new_jobid}).

%% delete an running job
do_delete_job(JobId, State) ->
    case do_get_job(JobId, State) of
        false ->
            {error, job_not_exist};
        {ok, Job = #job_info{pid = Pid}} ->
            % delete the job
            case hengm_job:stop(Pid, JobId) of
                ok ->
                    ets:delete(?TAB_JOBS, JobId),
                    ok;
                {error, R} ->
                    {error, R}
            end
    end.

%% get job info
do_get_job(JobId, _State) ->
    case ets:lookup(?TAB_JOBS, JobId) of
        [] ->
            false;
        [Job = #job_info{}] ->
            {ok, Job}
    end.

%% get all jobs
do_all(_State) ->
    ets:tab2list(?TAB_JOBS).
